package co.yixiang.yshop.module.member.service.user;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.*;
import co.yixiang.yshop.framework.common.enums.CommonStatusEnum;
import co.yixiang.yshop.module.infra.api.file.FileApi;
import co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillModifyDTO;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;
import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;
import co.yixiang.yshop.module.member.convert.user.UserConvert;
import co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;
import co.yixiang.yshop.module.member.dal.dataobject.userlevelgrowthvalueconfig.UserLevelGrowthValueConfigDO;
import co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;
import co.yixiang.yshop.module.member.enums.BillDetailEnum;
import co.yixiang.yshop.module.member.enums.LevelGrowthValueEnum;
import co.yixiang.yshop.module.member.service.integralrule.strategy.IntegralRuleContext;
import co.yixiang.yshop.module.member.service.userbill.UserBillService;
import co.yixiang.yshop.module.member.service.userinvitelog.UserInviteLogService;
import co.yixiang.yshop.module.member.service.userlevelconfig.UserLevelConfigService;
import co.yixiang.yshop.module.member.service.userlevelgrowthvalueconfig.UserLevelGrowthValueConfigService;
import co.yixiang.yshop.module.system.api.sms.SmsCodeApi;
import co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
import co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataRespVO;
import co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;
import co.yixiang.yshop.module.system.service.dict.DictDataService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

import static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;
import static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;
import static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;

/**
 * 会员 User Service 实现类
 *
 * @author yshop
 */
@Service
@Valid
@Slf4j
public class MemberUserServiceImpl extends ServiceImpl<MemberUserMapper, MemberUserDO> implements MemberUserService {

    @Resource
    private MemberUserMapper memberUserMapper;

    @Resource
    private DictDataService dictDataService;

    @Resource
    private FileApi fileApi;
    @Resource
    private SmsCodeApi smsCodeApi;

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private IntegralRuleContext integralRuleContext;
    @Resource
    private UserLevelGrowthValueConfigService growthValueConfigService;
    @Resource
    private UserLevelConfigService levelConfigService;
    @Resource
    private UserInviteLogService userInviteLogService;
    @Resource
    private UserBillService userBillService;
    @Resource
    private WxMaService wxMaService;


    @Override
    public MemberUserDO getUserByMobile(String mobile) {
        return memberUserMapper.selectByMobile(mobile);
    }

    @Override
    public List<MemberUserDO> getUserListByNickname(String nickname) {
        return memberUserMapper.selectListByNicknameLike(nickname);
    }

    @Override
    public MemberUserDO createUserIfAbsent(String mobile, String registerIp, String from, String invitationCode, String loginCode) {
        // 用户已经存在
        MemberUserDO user = StrUtil.isEmpty(mobile) ? null : memberUserMapper.selectByMobile(mobile);
        if (ObjectUtil.isNotNull(user)) {
            if (!StringUtils.isNotBlank(user.getRoutineOpenId()) && StringUtils.isNotBlank(loginCode)) {
                this.updateOpenId(user.getId(), loginCode);
            }
            return user;
        }
        // 用户不存在，则进行创建
        user = this.createUser(mobile, registerIp, from);
        if (StringUtils.isNotBlank(loginCode)) {
            this.updateOpenId(user.getId(), loginCode);
        }
        // 处理其他逻辑
        handlerAfterRegister(user, invitationCode);
        return user;
    }

    /**
     * 更新小程序openId
     *
     * @param id        用户id
     * @param loginCode 登录code
     */
    private void updateOpenId(Long id, String loginCode) {
        WxMaJscode2SessionResult session;
        try {
            session = wxMaService.getUserService().getSessionInfo(loginCode);
        } catch (WxErrorException e) {
            log.error(e.getMessage());
            throw exception(MINI_AUTH_LOGIN_BAD);
        }
        this.updateById(new MemberUserDO().setId(id).setRoutineOpenId(session.getOpenid()));
    }

    /**
     * 处理注册完成之后其他墨迹
     *
     * @param user           /
     * @param invitationCode 邀请码
     */
    private void handlerAfterRegister(MemberUserDO user, String invitationCode) {
        //注册积分
        integralRuleContext.execute(BillDetailEnum.REGISTER, user.getId());
        if (StrUtil.isBlank(invitationCode)) {
            return;
        }
        MemberUserDO parent = memberUserMapper.getByInvitationCode(invitationCode);
        if (Objects.isNull(parent)) {
            return;
        }
        //记录邀请日志
        userInviteLogService.createUserInviteLog(parent.getId(), user.getId());
        //赠送邀请人成长值
        addGrowthValue(parent.getId(), LevelGrowthValueEnum.INVITE.getType(), null);
    }


    private MemberUserDO createUser(String mobile, String registerIp, String from) {
        // 生成密码
        String password = IdUtil.fastSimpleUUID();
        // 插入用户
        MemberUserDO user = new MemberUserDO();
        user.setMobile(mobile);
        //随机一个昵称
        user.setNickname("yshop-" + RandomUtil.randomString(5));
        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
        user.setPassword(encodePassword(password)); // 加密密码
        user.setRegisterIp(registerIp);
        user.setLoginType(from);
        // 查字典获取默认头像
        DictDataRespVO defaultHead = dictDataService.getDefaultHead();
        user.setAvatar(defaultHead.getValue());
        //生成邀请码
        user.setInvitationCode(generateInvitationCode());
        memberUserMapper.insert(user);
        return user;
    }

    @Override
    public void updateUserLogin(Long id, String loginIp) {
        memberUserMapper.updateById(new MemberUserDO().setId(id)
                .setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));
    }

    @Override
    public MemberUserDO getUser(Long id) {
        return memberUserMapper.selectById(id);
    }

    @Override
    public AppUserQueryVo getAppUser(Long id) {
        return UserConvert.INSTANCE.convert3(memberUserMapper.selectById(id));
    }

    /**
     * 减去用户余额
     *
     * @param uid      uid
     * @param payPrice 金额
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void decPrice(Long uid, BigDecimal payPrice) {
        memberUserMapper.decPrice(payPrice, uid);
    }

    /**
     * 增加购买次数
     *
     * @param uid uid
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void incPayCount(Long uid) {
        memberUserMapper.incPayCount(uid);
    }


    @Override
    public List<MemberUserDO> getUserList(Collection<Long> ids) {
        return memberUserMapper.selectBatchIds(ids);
    }

    @Override
    public void updateUserNickname(Long userId, String nickname, String birthday, Integer sex) {
        MemberUserDO user = this.checkUserExists(userId);
        // 仅当新昵称不等于旧昵称时进行修改
//        if (nickname.equals(user.getNickname())){
//            return;
//        }
        MemberUserDO userDO = new MemberUserDO();
        userDO.setId(user.getId());
        userDO.setNickname(nickname);
        userDO.setBirthday(birthday);
        userDO.setSex(sex);
        memberUserMapper.updateById(userDO);
    }

    @Override
    public String updateUserAvatar(Long userId, InputStream avatarFile) throws Exception {
        this.checkUserExists(userId);
        // 创建文件
        String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));
        // 更新头像路径
        memberUserMapper.updateById(MemberUserDO.builder().id(userId).avatar(avatar).build());
        return avatar;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO) {
        // 检测用户是否存在
        checkUserExists(userId);
        // TODO yshop：oldMobile 应该不用传递

        // 校验旧手机和旧验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getOldMobile()).setCode(reqVO.getOldCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));
        // 使用新验证码
        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())
                .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));

        // 更新用户手机
        memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());
    }

    @Override
    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {
        return passwordEncoder.matches(rawPassword, encodedPassword);
    }

    /**
     * 更新用户余额
     *
     * @param uid   y用户id
     * @param price 金额
     */
    @Override
    public void incMoney(Long uid, BigDecimal price) {
        if (price != null && price.doubleValue() > 0) {
            memberUserMapper.incMoney(uid, price);
        }
    }

    @Override
//    @Async
    public void addIntegral(UserBillModifyDTO dto) {
        if (dto.getAfterAmount().compareTo(BigDecimal.ZERO) < 0) {
            throw exception(NOT_ENOUGH_INTEGRAL);
        }
        if (Objects.nonNull(dto.getAmount()) && dto.getAmount().compareTo(BigDecimal.ZERO) != 0) {
            BigDecimal integralAbs = dto.getAmount().abs();
            this.addIntegral(dto.getUid(), dto.getAmount());
            //账单记录
            userBillService.addBillLog(dto.getUid(), dto.getTypeEnum().getDesc(), dto.getCategoryEnum().getValue(),
                    dto.getTypeEnum().getValue(),
                    integralAbs,
                    dto.getAfterAmount(),
                    dto.getTypeEnum().getDesc(), String.valueOf(dto.getLinkId()), dto.getPmType().getValue());
        }
    }

    @Override
    public void addIntegral(Long userId, BigDecimal integral) {
        memberUserMapper.addIntegral(userId, integral);
    }

    @Override
    public void addConsumeAmount(Long userId, BigDecimal payPrice) {
        memberUserMapper.addConsumeAmount(userId,payPrice);
    }

    /**
     * 增加用户成长值
     *
     * @param userId        当前用户
     * @param type          类型
     * @param consumeAmount 消费金额(类型为消费)
     * @return 成长值
     */
    private Integer addGrowthValue(Long userId, String type, BigDecimal consumeAmount) {
        //查询对应成长配置
        UserLevelGrowthValueConfigDO growthValueConfig = growthValueConfigService.getByType(type);
        Integer growthValue = 0;
        if (type.equals(LevelGrowthValueEnum.INVITE.getType())) {
            growthValue = growthValueConfig.getGrowthValue();
        } else if (type.equals(LevelGrowthValueEnum.CONSUME.getType())) {
            growthValue = NumberUtil.mul(growthValueConfig.getGrowthValue(), consumeAmount).setScale(0, RoundingMode.DOWN).intValue();
        }
        if (growthValue > 0) {
            memberUserMapper.addGrowthValue(userId, growthValue);
            //升级
            levelConfigService.upgradeLevel(userId);
        }
        return null;
    }

    /**
     * 生成邀请码
     *
     * @return 唯一邀请码
     */
    private String generateInvitationCode() {
        String code;
        while (true) {
            code = RandomUtil.randomStringUpper(6);
            MemberUserDO user = memberUserMapper.getByInvitationCode(code);
            if (Objects.isNull(user)) {
                break;
            }
        }
        return code;
    }

    /**
     * 对密码进行加密
     *
     * @param password 密码
     * @return 加密后的密码
     */
    private String encodePassword(String password) {
        return passwordEncoder.encode(password);
    }

    @VisibleForTesting
    public MemberUserDO checkUserExists(Long id) {
        if (id == null) {
            return null;
        }
        MemberUserDO user = memberUserMapper.selectById(id);
        if (user == null) {
            throw exception(USER_NOT_EXISTS);
        }
        return user;
    }

}
